package cmich.cps.svn.utility;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import cmich.cps.svn.utility.gui.Prompts;

/**
 * The Assignment Class.
 *
 * @author Jonathan Kissinger <kissi1j@cmich.edu>
 */
public class Assignment {

	/** The system. */
	private UtilitySystem system = UtilitySystem.getInstance();

	/** The settings. */
	private Settings settings = Settings.getInstance();

	/** The prompts. */
	private Prompts prompts = Prompts.getInstance();

	/** The Constant LOGGER. */
	private final static Logger LOGGER = Logger.getLogger(Assignment.class.getName());

	/** The directory of this assignment. */
	private File directory;

	/**
	 * Instantiates a new assignment.
	 *
	 * @param myDirectory the directory of this assignment
	 */
	public Assignment(File myDirectory) {
		this.directory = myDirectory;
	}

	/**
	 * Create's the assignment directory then calls {@link #makeStudentDirs(List)}.
	 *
	 * @param studentList A list of student global ids
	 * @return true if creation of the assignment was successful
	 */
	public boolean create(List<String> studentList) {
		directory.mkdir();
		if(!directory.exists()) {
			String message = "Unable to add new assignment: " + this.directory; 
			LOGGER.warning(message);
			prompts.nonFatalError(message);
			return false;
		}
		return makeStudentDirs(studentList);
	}

	/**
	 * Adds a file to every student directory, within the assignment directory.  Creating
	 * the directories if necessary.  Also adds the files/directories to the repository
	 * with subversion.
	 * 
	 * @param studentList List of student global ids
	 * @param srcFile The file to be added
	 * @return true if the copy and creation of the student directories is successful, otherwise false
	 */
	public boolean addFile(List<String> studentList, File srcFile) {
		if(!makeStudentDirs(studentList)) {
			return false;
		}
		String fileName = srcFile.getName();
		if(fileName.endsWith(".java")) {
			fileName = "src" + File.separator + settings.getPackagePlusFileName(srcFile);
		}
		for(String studentID : studentList) {
			File studentDir = new File(this.directory + File.separator + studentID);
			LOGGER.finest("Attempting to add " + srcFile.getName() + " to assignment " + this);
			try {
				File destFile = new File(studentDir + File.separator + fileName);
				FileUtils.copyFile(srcFile, destFile);
			} catch (IOException e) {
				String msg = "Error copying " + srcFile.getName() + " to " + studentDir.getAbsolutePath() + ": " + e.toString();
				LOGGER.warning(msg);
				prompts.nonFatalError(msg);
			}
		}
		return true;
	}

	/**
	 * Creates the student directories for the assignment. Also adds them to the repository.
	 * 
	 * @param studentList List of student global ids
	 * @return true If directories successfully created
	 */
	private boolean makeStudentDirs(List<String> studentList) {
		for(String studentID : studentList) {
			File studentDir = new File(this.directory + File.separator + studentID);
			if(!studentDir.exists()) {
				if(!studentDir.mkdir()) {
					String msg = "Could not create student directory " + studentDir.getAbsolutePath();
					LOGGER.severe(msg);
					prompts.fatalError(msg);
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Adds a unit test to an assignment locally and then adds it to the repository.
	 * 
	 * @param srcFile The unit test to be added
	 * @return true if the add is successful, otherwise false
	 */
	public boolean addUnitTest(File srcFile) {
		File testDir = getTestDir();
		boolean result = false;
		if(!testDir.exists()) {
			if(!testDir.mkdir()) {
				String msg = "Unable to create folder " + testDir.getAbsolutePath();
				LOGGER.info(msg);
				prompts.nonFatalError(msg);
				return false;
			}
			result = true;
		}
		if(checkUnitTestFile(srcFile)) {
			String fileName = settings.getPackagePlusFileName(srcFile);
			File destFile = new File(testDir.getAbsolutePath() + File.separator + fileName);
			LOGGER.finest("Attempting to add " + destFile.getAbsolutePath() + " to assignment " + this);
			copyFile(srcFile, destFile);
			result = true;
		}
		return result;
	}

	/**
	 * Determines if this assignment is past its {@link #getDueDate()}.
	 * 
	 * @return Returns true if this assignment is past due, otherwise false
	 */
	public boolean isPastDue() {
		boolean result = getDueDate().before(new Date());
		return result;
	}

	/**
	 * Copies a file.
	 *
	 * @param srcFile The source file to be copied
	 * @param destFile The destination file to be copied to
	 */
	private void copyFile(File srcFile, File destFile) {
		LOGGER.finest("Copying file: " + srcFile.getAbsolutePath() + " to " + destFile.getAbsolutePath());
		try {
			FileUtils.copyFile(srcFile, destFile);
		} catch (IOException e) {
			LOGGER.info(e.toString());
			prompts.nonFatalError(e.toString());
			return;
		}
	}

	/**
	 * Checks a given unit test file to see if meets all specifications.
	 *
	 * @param srcFile The unit test file to be checked
	 * @return true if the given file passes the specifications, false otherwise
	 */
	public boolean checkUnitTestFile(File srcFile) {
		if(!srcFile.getName().endsWith(".java")) {
			LOGGER.info("User attempted to add a non-java file to the unit tests.");
			prompts.nonFatalError(srcFile.getName() + " is not a java file and cannot be used as a unit test.");
			return false;
		}
		String name = srcFile.getName().toLowerCase();
		if(!name.contains("test") || name.contains("$")) {
			String msg = "Chosen unit test file " + srcFile.getName() + " does not contain the word \"test\" in the filename.";
			LOGGER.info(msg);
			prompts.nonFatalError(msg);
			return false;
		}
		String source = settings.getJavaSource(srcFile);
		if(source==null) {
			return false;
		}
		String myPackage = settings.getPackage(source);
		if(myPackage==null || !myPackage.toLowerCase().contains(".test")) {
			String msg = "Chosen unit test file " + srcFile.getName() + " is in an invalid package: " + myPackage + ", package must contain '.test', case doesn't matter.";
			LOGGER.info(msg);
			prompts.nonFatalError(msg);
			return false;
		}
		return true;
	}

	/**
	 * Gets the path.
	 *
	 * @return String representation of the path for {@link #directory}
	 */
	public String getPath() {
		return directory.getPath();
	}

	public String getEnclosedPath() {
		return settings.enclose(directory.getPath());
	}

	/**
	 * Gets the allow late commits.
	 *
	 * @return The allowLateCommits subversion property set on {@link #directory}
	 */
	public Boolean getAllowLateCommits() {
		Boolean result = Boolean.parseBoolean(system.getProperty("allowLateCommits", getEnclosedPath()));
		LOGGER.fine(this + " allowLateCommits set to " + result);
		return result;
	}

	/**
	 * Gets the require commit log.
	 *
	 * @return The requireCommitLog subversion property set on {@link #directory}
	 */
	public Boolean getRequireCommitLog() {
		Boolean result = Boolean.parseBoolean(system.getProperty("requireCommitLog", getEnclosedPath()));
		LOGGER.fine(this + " requireCommitLog set to " + result);
		return result;
	}

	/**
	 * Gets the due date.
	 *
	 * @return The dueDate subversion property set on {@link #directory}
	 */
	public Date getDueDate() {
		String dueDateString = system.getProperty("dueDate", getEnclosedPath());
		Date dueDate = null;
		try {
			dueDate = settings.sdf.parse(dueDateString);
		} catch (ParseException e) {
			LOGGER.warning("Invalid format for due date: " + dueDateString);
			prompts.nonFatalError("Invalid format for due date: " + dueDateString + "<br><br>Should match the format: yyyy-MM-dd 'at' hh:mm:ss aaaa");
		}
		return dueDate;
	}

	/**
	 * Gets the due date string.
	 *
	 * @return String representation of {@link #getDueDate()}
	 */
	public String getDueDateString() {
		return system.getProperty("dueDate", getEnclosedPath());
	}

	/**
	 * Gets the file.
	 *
	 * @return {@link #directory}
	 */
	public File getFile() {
		return this.directory;
	}

	/**
	 * Gets the test directory.
	 *
	 * @return File pointing to the unit tests directory inside {@link #directory}
	 */
	public File getTestDir() {
		return new File(this.directory + File.separator + "unit_tests");
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return directory.getName();
	}
}
